﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;

namespace Mrc.Common.SystemInfo
{
    public class MachineInfoManager
    {
        public MachineInfo _machineInfo=new MachineInfo(); 
        
        public void Init()  
        {
            RefreshCpuInfo();
            RefreshMemInfo();
            if(PlatformTypeInfo.IsLinux) LoadLinuxInfo();
            if(PlatformTypeInfo.IsWindows) LoadWindowsInfo();        
        }
 
        public void FreshCpuAndMoney() 
        {
            RefreshCpuInfo();
            RefreshMemInfo();
        }
        /// <summary>
        /// 加载linux信息
        /// </summary>
        private void LoadLinuxInfo()
        {
            var str = SystemHelper.GetLinuxName();
            if(!str.IsNullOrEmpty()) _machineInfo.OSName = str;
            // 树莓派的Hardware无法区分P0/P4
            var dic = ReadInfo("/proc/cpuinfo");
            if(dic != null)
            {
                if (dic.TryGetValue("Hardware", out str)||dic.TryGetValue("cpu model", out str)||dic.TryGetValue("model name", out str)) _machineInfo.Processor = str;
                if (dic.TryGetValue("Model", out str)) _machineInfo.Product = str;
                if (dic.TryGetValue("Serial", out str)) _machineInfo.CpuID = str;
            }
            var mid = "/etc/machine-id";
            if (!File.Exists(mid)) mid = "/var/lib/dbus/machine-id";
            if (File.Exists(mid)) _machineInfo.Guid = File.ReadAllText(mid).Trim();
            var file = "/sys/class/dmi/id/product_uuid";
            if (File.Exists(file)) _machineInfo.UUID = File.ReadAllText(file).Trim();
            file = "/sys/class/dmi/id/product_name";
            if (File.Exists(file)) _machineInfo.Product = File.ReadAllText(file).Trim();
            var disks = SystemHelper.GetFiles("/dev/disk/by-id", true);
            if (disks.Count == 0) disks = SystemHelper.GetFiles("/dev/disk/by-uuid", false);
            if (disks.Count > 0) _machineInfo.DiskID = string.Join(",",disks);
            var dmi = SystemHelper.Execute("dmidecode")?.SplitAsDictionary(":", "\n");
            if (dmi != null)
            {
                if (dmi.TryGetValue("ID", out str)) _machineInfo.CpuID = str.Replace(" ", null);
                if (dmi.TryGetValue("UUID", out str)) _machineInfo.UUID = str;
                if (dmi.TryGetValue("Product Name", out str)) _machineInfo.Product = str;
                //if (TryFind(dmi, new[] { "Serial Number" }, out str)) Guid = str;
            }
            // 从release文件读取产品
            var prd = GetProductByRelease();
            if (!prd.IsNullOrEmpty()) _machineInfo.Product = prd;
        }
        /// <summary>
        /// 加载windows信息
        /// </summary>
        private void LoadWindowsInfo()
        {
            var str = "";
            var os = SystemHelper.ReadWmic("os", "Caption", "Version");
            if (os != null)
            {
                if (os.TryGetValue("Caption", out str)) _machineInfo.OSName = str.TrimStart('M').Trim();
                if (os.TryGetValue("Version", out str)) _machineInfo.OSVersion = str;
            }
            var csproduct = SystemHelper.ReadWmic("csproduct", "Name", "UUID");
            if (csproduct != null)
            {
                if (csproduct.TryGetValue("Name", out str)) _machineInfo.Product = str;
                if (csproduct.TryGetValue("UUID", out str)) _machineInfo.UUID = str;
            }
            var disk = SystemHelper.ReadWmic("diskdrive", "serialnumber");
            if (disk != null)
            {
                if (disk.TryGetValue("serialnumber", out str)) _machineInfo.DiskID = str?.Trim();
            }
            // 不要在刷新里面取CPU负载，因为运行wmic会导致CPU负载很不准确，影响测量
            var cpu = SystemHelper.ReadWmic("cpu", "Name", "ProcessorId", "LoadPercentage");
            if (cpu != null)
            {
                if (cpu.TryGetValue("Name", out str)) _machineInfo.Processor = str;
                if (cpu.TryGetValue("ProcessorId", out str)) _machineInfo.CpuID = str;
                if (cpu.TryGetValue("LoadPercentage", out str)) _machineInfo.CpuRate = (Single)(str.ToDouble() / 100);
            }
            // 从注册表读取 MachineGuid
            str = SystemHelper.Execute("reg", @"query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid");
            if (!str.IsNullOrEmpty() && str.Contains("REG_SZ")) _machineInfo.Guid = str.Substring(0).Trim();
        }

        /// <summary>
        /// 刷新cpu信息
        /// </summary>
        private void RefreshCpuInfo()
        {
            if(PlatformTypeInfo.IsLinux)
            {
                var file = "/proc/loadavg";
                if (File.Exists(file)) _machineInfo.CpuRate = (Single)File.ReadAllText(file).Substring(null, " ").ToDouble();
            }
            if(PlatformTypeInfo.IsWindows)
            {
                GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
                var current = new SystemTime
                {
                    IdleTime = idleTime.ToLong(),
                    KernelTime = kernelTime.ToLong(),
                    UserTime = userTime.ToLong(),
                };
                if (_systemTime != null)
                {
                    var idle = current.IdleTime - _systemTime.IdleTime;
                    var kernel = current.KernelTime - _systemTime.KernelTime;
                    var user = current.UserTime - _systemTime.UserTime;
                    var total = kernel + user;
                   _machineInfo.CpuRate = (total == 0 ? 0 : ((Single)((Double)(total - idle) / total))*100);
                }
                _systemTime = current;
            }
        }

        private class SystemTime
        {
            public Int64 IdleTime;
            public Int64 KernelTime;
            public Int64 UserTime;
        }
        private SystemTime _systemTime;
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern Boolean GetSystemTimes(out FILETIME idleTime, out FILETIME kernelTime, out FILETIME userTime);
        private struct FILETIME
        {
            public UInt32 Low;
            public UInt32 High;
            public FILETIME(Int64 time)
            {
                Low = (UInt32)time;
                High = (UInt32)(time >> 32);
            }
            public Int64 ToLong() => (Int64)(((UInt64)High << 32) | Low);
        }

        /// <summary>
        /// 刷新内存信息
        /// </summary>
        private void RefreshMemInfo()
        {
            if (PlatformTypeInfo.IsLinux)
            {
                var dic = ReadInfo("/proc/meminfo");
                if(dic != null)
                {
                    if (dic.TryGetValue("MemTotal", out var str)) _machineInfo.Memory = (UInt64) str.TrimEnd(" kB".ToCharArray()).ToInt() * 1024;
                    if (dic.TryGetValue("MemAvailable", out str) ||dic.TryGetValue("MemFree", out str)) _machineInfo.AvailableMemory = (UInt64)str.TrimEnd(" kB".ToCharArray()).ToInt() * 1024;
                }
            }
            else
            {
                MEMORYSTATUSEX ms = default;
                ms.Init();
                if (GlobalMemoryStatusEx(ref ms))
                {
                    _machineInfo.Memory = ms.ullTotalPhys;
                    _machineInfo.AvailableMemory = ms.ullAvailPhys;
                }
            }
        }
        /// <summary>
        /// windows内存信息
        /// </summary>
        /// <param name="lpBuffer"></param>
        /// <returns></returns>
        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [SecurityCritical]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern Boolean GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
        internal struct MEMORYSTATUSEX
        {
            internal UInt32 dwLength;

            internal UInt32 dwMemoryLoad;

            internal UInt64 ullTotalPhys;

            internal UInt64 ullAvailPhys;

            internal UInt64 ullTotalPageFile;

            internal UInt64 ullAvailPageFile;

            internal UInt64 ullTotalVirtual;

            internal UInt64 ullAvailVirtual;

            internal UInt64 ullAvailExtendedVirtual;

            internal void Init() => dwLength = checked((UInt32)Marshal.SizeOf(typeof(MEMORYSTATUSEX)));
        }

        private String GetProductByRelease()
        {
            var di = "/etc/".AsDirectory();
            if (!di.Exists) return null;

            foreach (var fi in di.GetFiles("*-release"))
            {
                if (!fi.Name.EqualIgnoreCase("redhat-release", "debian-release", "os-release", "system-release"))
                {
                    var dic = File.ReadAllText(fi.FullName).SplitAsDictionary("=", "\n", true);
                    if (dic.TryGetValue("BOARD", out var str)) return str;
                    if (dic.TryGetValue("BOARD_NAME", out str)) return str;
                }
            }
            return null;
        }

        private  IDictionary<String,String> ReadInfo(String file, Char separate = ':')
        {
            if (file.IsNullOrEmpty() || !File.Exists(file)) return null;

            var dic = new Dictionary<String, String>();
            using var reader = new StreamReader(file);
            while (!reader.EndOfStream)
            {
                // 按行读取
                var line = reader.ReadLine();
                if (line != null)
                {
                    // 分割
                    var p = line.IndexOf(separate);
                    if (p > 0)
                    {
                        var key = line.Substring(0, p).Trim();
                        var value = line.Substring(p + 1).Trim();
                        dic[key] = value;
                    }
                }
            }
            return dic;
        }
    }

   
}
